/**
 *    Copyright 2009-2017 the original author or authors.
 *
 *    Licensed under the Apache License, Version 2.0 (the "License");
 *    you may not use this file except in compliance with the License.
 *    You may obtain a copy of the License at
 *
 *       http://www.apache.org/licenses/LICENSE-2.0
 *
 *    Unless required by applicable law or agreed to in writing, software
 *    distributed under the License is distributed on an "AS IS" BASIS,
 *    WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 *    See the License for the specific language governing permissions and
 *    limitations under the License.
 */
package org.apache.ibatis.scripting.xmltags;

import java.util.regex.Pattern;

import org.apache.ibatis.parsing.GenericTokenParser;
import org.apache.ibatis.parsing.TokenHandler;
import org.apache.ibatis.scripting.ScriptingException;
import org.apache.ibatis.type.SimpleTypeRegistry;

/**该类与StaticTextSqlNode类不同的是，当静态文本中包含了${}占位符时，说明${}需要在Mapper调用时将${}替换为具体的参数值
 *
 * @author Clinton Begin
 */
public class TextSqlNode implements SqlNode {
	
	private final String text;
	private final Pattern injectionFilter;

	public TextSqlNode(String text) {
		this(text, null);
	}

	public TextSqlNode(String text, Pattern injectionFilter) {
		this.text = text;
		this.injectionFilter = injectionFilter;
	}

	/**判断是否是动态sql
	 * @return
	 */
	public boolean isDynamic() {
		DynamicCheckerTokenParser checker = new DynamicCheckerTokenParser();
		GenericTokenParser parser = createParser(checker);
		parser.parse(text);
		return checker.isDynamic();
	}

	@Override
	public boolean apply(DynamicContext context) {
		GenericTokenParser parser = createParser(new BindingTokenParser(context, injectionFilter));
		context.appendSql(parser.parse(text));
		return true;
	}

	private GenericTokenParser createParser(TokenHandler handler) {
		// 这个handler就是用来解析"${}"的
		return new GenericTokenParser("${", "}", handler);
	}

	// 绑定记号解析器
	private static class BindingTokenParser implements TokenHandler {

		private DynamicContext context;
		private Pattern injectionFilter;

		public BindingTokenParser(DynamicContext context, Pattern injectionFilter) {
			this.context = context;
			this.injectionFilter = injectionFilter;
		}

		@Override
		public String handleToken(String content) {
			// 获取Mybatis的内置参数_parameter，_parameter属性中保存所有参数信息
			Object parameter = context.getBindings().get("_parameter");
			if (parameter == null) {
				context.getBindings().put("value", null);
			} else if (SimpleTypeRegistry.isSimpleType(parameter.getClass())) {
				// 将参数对象添加到ContextMap中
				context.getBindings().put("value", parameter);
			}
			// 通过OGNL表达式获取参数值
			Object value = OgnlCache.getValue(content, context.getBindings());
			String srtValue = (value == null ? "" : String.valueOf(value)); // issue #274 return "" instead of "null"
			checkInjection(srtValue);
			return srtValue;
		}

		/**检查是否匹配正则表达式
		 * @param value
		 */
		private void checkInjection(String value) {
			if (injectionFilter != null && !injectionFilter.matcher(value).matches()) {
				throw new ScriptingException("Invalid input. Please conform to regex" + injectionFilter.pattern());
			}
		}
	}

	// 动态SQL检查器
	private static class DynamicCheckerTokenParser implements TokenHandler {

		private boolean isDynamic;

		public DynamicCheckerTokenParser() {
			// Prevent Synthetic Access
		}

		public boolean isDynamic() {
			return isDynamic;
		}

		@Override
		public String handleToken(String content) {
			// 灰常简单，设置isDynamic为true，即调用了这个类就必定是动态sql？？？
			this.isDynamic = true;
			return null;
		}
	}

}